
package com.bangcommunity.bbframe.dynconfig.lock;

import com.bangcommunity.bbframe.client.zookeeper.ZKClient;
import com.bangcommunity.bbframe.common.base.MainConfigLoader;
import com.bangcommunity.bbframe.common.exception.BaseRtmException;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.framework.recipes.locks.InterProcessReadWriteLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * Created by tanghc on 17/1/23.
 */
public class ZkLock {

    static final Logger logger = LoggerFactory.getLogger(ZkLock.class);
    private static ConcurrentMap<String, InterProcessReadWriteLock> locks = new ConcurrentHashMap<>();

    private ZkLock() {
    }

    private static class ZkLockHolder {

        private static ZkLock instance = new ZkLock();
    }

    public static ZkLock getInstance() {

        return ZkLockHolder.instance;
    }

    /**
     * zk全局锁,一次获取不到等待1ms继续获取,最长等待maxWaitMs
     * 
     * @param lock
     *            锁标示
     * @param maxWaitMs
     *            获取锁最长等待的时间,毫秒,超过这个时间则获取锁失败
     * @return
     */
    public boolean acquireLock(final String lock, final long maxWaitMs) {

        logger.debug("acquireLock start:[lock={}][maxWaitMs={}]", lock, maxWaitMs);
        InterProcessMutex wLock = getWLock(lock);
        if (null == wLock) {
            return false;
        }
        logger.debug("acquireLock start:[wLock={}]]", wLock);
        try {
            return wLock.acquire(maxWaitMs, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            logger.error("获取锁资源出现异常|lockKey:[{}]", lock, e);
        }
        return false;
    }

    /**
     * 解锁
     */
    public void releaseLock(final String lock) throws Exception {

        logger.debug("releaseLock start:[lock={}]", lock);
        InterProcessMutex wLock = getWLock(lock);
        if (null == wLock) {
            throw new RuntimeException("release zk lock exception without lock");
        }
        wLock.release();
    }
    public <T> T execute(final String lockKey,final long maxWaitMs,Supplier<T> supplier){
        return execute(lockKey,maxWaitMs,supplier,null);
    }
    public <T> T execute(final String lockKey,final long maxWaitMs,Supplier<T> supplier,String exception){
        try {
            boolean lock = acquireLock(lockKey, maxWaitMs);
            if(lock){
                return supplier.get();
            }
            throw new BaseRtmException(null != exception?exception:"系统繁忙,请稍后重试");
        } finally {
            try {
                releaseLock(lockKey);
            } catch (Exception e) {
                logger.info("release lock exception:{},{}",lockKey,e.getMessage());
            }
        }
    }

    private InterProcessReadWriteLock getWRLock(String key) {

        String lockPath = getLockPath(key);
        InterProcessReadWriteLock interProcessReadWriteLock = locks.get(lockPath);
        if (null == interProcessReadWriteLock) {
            CuratorFramework client = ZKClient.getClient();
            interProcessReadWriteLock = new InterProcessReadWriteLock(client, lockPath);
            locks.putIfAbsent(lockPath, interProcessReadWriteLock);
            interProcessReadWriteLock = locks.get(lockPath);// 保证是统一个锁
        }
        return interProcessReadWriteLock;
    }

    private InterProcessMutex getWLock(String key) {

        InterProcessReadWriteLock interProcessReadWriteLock = getWRLock(key);
        if (null != interProcessReadWriteLock) {
            return interProcessReadWriteLock.writeLock();
        }
        return null;
    }

    private InterProcessMutex getRLock(String key) {

        InterProcessReadWriteLock interProcessReadWriteLock = getWRLock(key);
        if (null != interProcessReadWriteLock) {
            return interProcessReadWriteLock.readLock();
        }
        return null;
    }

    private String getLockPath(String key) {

        return new StringBuilder().append("/biz/lock/").append(MainConfigLoader.getInstance().getProductCode())
                .append("/").append(MainConfigLoader.getInstance().getAppCode()).append("/").append(key).toString();
    }
}
